using System;
using System.Collections.Generic;
using System.Linq;

namespace Agatha.Common
{
	/// <summary>
	/// Originally written by Tom Ceulemans
	/// </summary>
	public class ResponseReceiver
	{
		private readonly Action<ReceivedResponses> responseReceivedCallback;
		private readonly Action<ExceptionInfo, ExceptionType> exceptionAndTypeOccuredCallback;
		private readonly Action<ExceptionInfo> exceptionOccurredCallback;
		private readonly Dictionary<string, int> keyToResultPositions;


		public ResponseReceiver(Action<ReceivedResponses> responseReceivedCallback, Action<ExceptionInfo> exceptionOccurredCallback,
			Dictionary<string, int> keyToResultPositions)
		{
			if (responseReceivedCallback == null) throw new ArgumentNullException("responseReceivedCallback");
			if (exceptionOccurredCallback == null) throw new ArgumentNullException("exceptionOccurredCallback");

			this.responseReceivedCallback = responseReceivedCallback;
			this.exceptionOccurredCallback = exceptionOccurredCallback;
			this.keyToResultPositions = keyToResultPositions;
		}

		public ResponseReceiver(
			Action<ReceivedResponses> responseReceivedCallback,
			Action<ExceptionInfo, ExceptionType> exceptionAndTypeOccuredCallback,
			Dictionary<string, int> keyToResultPositions)
		{
			if (responseReceivedCallback == null) throw new ArgumentNullException("responseReceivedCallback");
			if (exceptionAndTypeOccuredCallback == null) throw new ArgumentNullException("exceptionAndTypeOccuredCallback");

			this.responseReceivedCallback = responseReceivedCallback;
			this.exceptionAndTypeOccuredCallback = exceptionAndTypeOccuredCallback;
			this.keyToResultPositions = keyToResultPositions;
		}

		public void ReceiveResponses(ProcessRequestsAsyncCompletedArgs args)
		{
			if (HasException(args))
			{
				HandleException(args);
			}
			else
			{
				var disposable = responseReceivedCallback.Target as Disposable;

				if (disposable == null || !disposable.IsDisposed)
				{
					responseReceivedCallback(new ReceivedResponses(args.Result, keyToResultPositions));
				}
			}
		}

		private void HandleException(ProcessRequestsAsyncCompletedArgs args)
		{
			var disposable = responseReceivedCallback.Target as Disposable;

			if (disposable == null || !disposable.IsDisposed)
			{
				var exception = GetException(args);

				if (exceptionOccurredCallback != null)
				{
					exceptionOccurredCallback(exception);
				}
				else if (exceptionAndTypeOccuredCallback != null)
				{
					var exceptionType = GetExceptionType(args);

					exceptionAndTypeOccuredCallback(exception, exceptionType);
				}
				else
				{
					responseReceivedCallback(new ReceivedResponses(args.Result, keyToResultPositions));
				}
			}
		}

		private static bool HasException(ProcessRequestsAsyncCompletedArgs args)
		{
			if (args.Error == null)
			{
				return args.Result.Any(r => r.Exception != null);
			}

			return true;
		}

		private static ExceptionInfo GetException(ProcessRequestsAsyncCompletedArgs args)
		{
			if (args.Error == null)
			{
				var responseWithException = GetFirstException(args.Result);
				if (responseWithException != null)
				{
					return responseWithException.Exception;
				}

				return null;
			}

			return new ExceptionInfo(args.Error);
		}

		private static ExceptionType GetExceptionType(ProcessRequestsAsyncCompletedArgs args)
		{
			if (args.Error == null)
			{
				var responseWithException = GetFirstException(args.Result);
				if (responseWithException != null)
				{
					return responseWithException.ExceptionType;
				}

				return ExceptionType.Unknown;
			}

			return ExceptionType.Unknown;
		}

		private static Response GetFirstException(IEnumerable<Response> responsesToCheck)
		{
			return responsesToCheck.FirstOrDefault(r => r.Exception != null);
		}
	}
}